#import "draw.h"

/* Optimally viewed in a wide window.  Make your window big enough so that this comment fits on one line without wrapping. */

/*
 * Image is a simple graphic which takes PostScript or
 * TIFF images and draws them in a bounding box (it scales
 * the image if the bounding box is changed).  It is
 * implemented using the NXImage class.  Using NXImage
 * here is especially nice since it images its PostScript
 * in a separate context (thus, any errors that PostScript
 * generates will not affect our main drawing context).
 */

@implementation Image : Graphic

/* Initialize the class */

+ initialize
{
    [Image setVersion:7];
    return self;
}

/* Factory methods. */

+ highlightedLinkButtonImage:(NXSize *)size
/*
 * Just makes an NXLinkButtonH NXImage the same size as
 * the size passed in.  I suppose this could just be a
 * function.
 */
{
    static NXImage *retval = nil;
    if (!retval) {
	retval = [[NXImage findImageNamed:"NXLinkButtonH"] copy];
	[retval setScalable:YES];
	[retval setDataRetained:YES];
    }
    [retval setSize:size];
    return retval;
}

+ (BOOL)canInitFromPasteboard:(Pasteboard *)pboard
{
    return [NXImage canInitFromPasteboard:pboard];
}

static BOOL checkImage(NXImage *anImage)
/*
 * Locking focus on an NXImage forces it to draw and thus verifies
 * whether there are any PostScript or TIFF errors in the source of
 * the image.  lockFocus returns YES only if there are no errors.
 */
{
    if ([anImage lockFocus]) {
	[anImage unlockFocus];
	return YES;
    }
    return NO;
}

/* Creation/Initialization Methods */

- init
/*
 * This creates basically an "empty" Image.
 * This is the designated initializer for Image.
 * Be careful, however, because by the time this
 * returns, a newly initialized Image may not be
 * fully initialized (it'll be "valid," just not
 * necessarily fully initialized).  If you want that
 * behaviour, override finishedWithInit.
 */
{
    [super init];
    originalSize.width = originalSize.height = 1.0;
    bounds.size = originalSize;
    return self;
}

- finishedWithInit
/*
 * Called when a newly initialized Image is fully
 * initialized and ready to roll.  For subclassers
 * only.
 */
{
    return self;
}

- initEmpty
/*
 * Creates a blank Image.
 */
{
    [self init];
    return [self finishedWithInit];
}

- initFromStream:(NXStream *)stream
/*
 * Creates a new NXImage and sets it to be scalable and to retain
 * its data (which means that when we archive it, it will actually
 * write the TIFF or PostScript data into the stream).
 */
{
    [self init];

    if (stream) {
	image = [NXImage allocFromZone:[self zone]];
	if ((image = [image initFromStream:stream])) {
	    [image setDataRetained:YES];
	    if (checkImage(image)) {
		[image getSize:&originalSize];
		[image setScalable:YES];
		bounds.size = originalSize;
		return [self finishedWithInit];
	    }
	}
    }

    [self free];

    return nil;
}

- initFromPasteboard:(Pasteboard *)pboard;
/*
 * Creates a new NXImage and sets it to be scalable and to retain
 * its data (which means that when we archive it, it will actually
 * write the TIFF or PostScript data into the stream).
 */
{
    [self init];

    if (pboard) {
	image = [NXImage allocFromZone:[self zone]];
	if ((image = [image initFromPasteboard:pboard])) {
	    [image setDataRetained:YES];
	    if (checkImage(image)) {
		[image getSize:&originalSize];
		[image setScalable:YES];
		bounds.size = originalSize;
		return [self finishedWithInit];
	    }
	}
    }

    [self free];

    return nil;
}

- initFromFile:(const char *)file
/*
 * Creates an NXImage by reading data from an .eps or .tiff file.
 */
{
    [self init];

    image = [[NXImage allocFromZone:[self zone]] init];
    if ([image loadFromFile:file]) {
	[image setDataRetained:YES];
	if (checkImage(image)) {
	    [image getSize:&originalSize];
	    [image setScalable:YES];
	    bounds.size = originalSize;
	    return [self finishedWithInit];
	}
    }

    [self free];

    return nil;
}

- doInitFromImage:(NXImage *)anImage
/*
 * Common code for initFromImage: and unarchiving.
 */
{
    if (anImage) {
	image = anImage;
	[image getSize:&originalSize];
	[image setScalable:YES];
	[image setDataRetained:YES];
	bounds.size = originalSize;
    } else {
	[self free];
	self = nil;
    }
    return self;
}

- initFromImage:(NXImage *)anImage
/*
 * Initializes an Image from a specific NXImage.
 */
{
    [self init];
    return [[self doInitFromImage:anImage] finishedWithInit];
}

- initFromIcon:(NXImage *)anImage
/*
 * Same as initFromImage:, but we remember that this particular
 * NXImage was actually a file icon (which enables us to double-click
 * on it to open the icon, see handleEvent:).
 */
{
    if ([self initFromImage:anImage]) {
	amIcon = YES;
	return self;
    } else {
	return nil;
    }
}

- initWithLinkButton
/*
 * Creates an image which is just the link button.
 * This is only applicable with Object Links.
 */
{
    if ([self initFromImage:[[NXImage findImageNamed:"NXLinkButton"] copy]]) {
	amLinkButton = YES;
	return self;
    } else {
	return nil;
    }
}

- (NXRect)resetImage:(NXImage *)newImage
/*
 * Called by the "reinit" methods to reset all of our instance
 * variables based on using a new NXImage for our image.
 */
{
    NXRect eBounds, neBounds;

    [image free];
    image = newImage;
    [self getExtendedBounds:&eBounds];
    [image getSize:&neBounds.size];
    neBounds.size.width *= bounds.size.width / originalSize.width;
    neBounds.size.height *= bounds.size.height / originalSize.height;
    neBounds.origin.x = bounds.origin.x - floor((neBounds.size.width - bounds.size.width) / 2.0 + 0.5);
    neBounds.origin.y = bounds.origin.y - floor((neBounds.size.height - bounds.size.height) / 2.0 + 0.5);
    [self setBounds:&neBounds];
    [self getExtendedBounds:&neBounds];
    NXUnionRect(&eBounds, &neBounds);
    [image setDataRetained:YES];
    [image getSize:&originalSize];
    [image setScalable:YES];

    return neBounds;
}

- (NXRect)reinitFromPasteboard:(Pasteboard *)pboard
/*
 * Reset all of our instance variable based on extract an
 * NXImage from data in the the passed pboard.  Happens when
 * we update a link through Object Links.
 */
{
    NXRect neBounds;
    NXImage *newImage;

    newImage = [NXImage allocFromZone:[self zone]];
    if ((newImage = [newImage initFromPasteboard:pboard])) {
	[newImage setDataRetained:YES];
	if (checkImage(newImage)) return [self resetImage:newImage];
    }

    [newImage free];
    neBounds.origin.x = neBounds.origin.y = 0.0;
    neBounds.size.width = neBounds.size.height = 0.0;

    return neBounds;
}

- (NXRect)reinitFromFile:(const char *)file
/*
 * Reset all of our instance variable based on extract an
 * NXImage from the data in the passed file.  Happens when
 * we update a link through Object Links.
 */
{
    NXRect neBounds;
    NXImage *newImage;

    newImage = [[NXImage allocFromZone:[self zone]] init];
    if ([newImage loadFromFile:file]) {
	[newImage setDataRetained:YES];
	if (checkImage(newImage)) return [self resetImage:newImage];
    }

    [newImage free];
    neBounds.origin.x = neBounds.origin.y = 0.0;
    neBounds.size.width = neBounds.size.height = 0.0;

    return neBounds;
}

/* All those allocation/initialization method and only this one free method. */

- free
{
    [image free];
    return [super free];
}

/* Link methods */

- setLink:(NXDataLink *)aLink
/*
 * It's "might" be linked because we're linked now, but might
 * have our link broken in the future and the mightBeLinked flag
 * is only advisory and is never cleared.  It is used just so that
 * we know we might want to try to reestablish a link with this
 * Graphic after a cut/paste.  No biggie if there really is no
 * link associated with this any more.  In gvLinks.m, see
 * readLinkForGraphic:fromPasteboard:useNewIdentifier:, and in
 * gvPasteboard.m, see pasteFromPasteboard:andLink:at:.
 * If this Image is a link button, then we obviously never need
 * to update the link because we don't actually show the data
 * associated with the link (we just show that little link button).
 */
{
    NXDataLink *oldLink = link;
    link = aLink;
    gFlags.mightBeLinked = YES;
    if (amLinkButton) [link setUpdateMode:NX_UpdateNever];
    return oldLink;
}

- (NXDataLink *)link
{
    return link;
}

/* Event-handling */

- trackLinkButton:(NXEvent *)event at:(const NXPoint *)startPoint inView:(View *)view
/*
 * This method tracks that little link button.  Note that the link button is a diamond,
 * but we track the whole rectangle.  This is unfortunate, but we can't be sure that,
 * in the future, the shape of the link button might not change (thus, what we really
 * need is a NeXTSTEP function to track the thing!).  Anyway, we track it and if the 
 * mouse goes up inside the button, we openSource on the link (we wouldn't be here if
 * we didn't have a link).
 */
{
    NXPoint p;
    NXImage *realImage, *highImage, *imageToDraw;

    p = *startPoint;
    realImage = image;
    highImage = [[self class] highlightedLinkButtonImage:&bounds.size];
    image = imageToDraw = highImage;
    [self draw];
    [[view window] flushWindow];
    do {
	event = [NXApp getNextEvent:NX_MOUSEDRAGGEDMASK|NX_MOUSEUPMASK];
	p = event->location;
	[view convertPoint:&p fromView:nil];
	imageToDraw = NXMouseInRect(&p, &bounds, NO) ? highImage : realImage;
	if (imageToDraw != image) {
	    image = imageToDraw;
	    [self draw];
	    [[view window] flushWindow];
	}
    } while (event->type != NX_MOUSEUP);

    if (imageToDraw == highImage) {
	[link openSource];
	image = realImage;
	[self draw];
	[[view window] flushWindow];
    }

    return self;
}

- (BOOL)handleEvent:(NXEvent *)event at:(const NXPoint *)p inView:(View *)view
{
    if (NXMouseInRect(p, &bounds, NO)) {
	if (amLinkButton && !gFlags.selected && !(event->flags & (NX_CONTROLMASK|NX_SHIFTMASK|NX_ALTERNATEMASK))) {
	    [self trackLinkButton:event at:p inView:view];
	    return YES;
	} else if (link && (event->data.mouse.click == 2) && (amIcon || (event->flags & NX_CONTROLMASK))) {
	    [NXApp getNextEvent:NX_MOUSEUPMASK];
	    [link openSource];
	    return YES;
	}
    }
    return NO;
}

/* Methods overridden from superclass to support links. */

- (int)cornerMask
/*
 * Link buttons are too small to have corners AND sides, so
 * we only let link buttons have knobbies on the corners.
 */
{
    if (amLinkButton) {
	return LOWER_LEFT_MASK|UPPER_LEFT_MASK|UPPER_RIGHT_MASK|LOWER_RIGHT_MASK;
    } else {
	return [super cornerMask];
    }
}

- (NXRect *)getExtendedBounds:(NXRect *)theRect
/*
 * We have to augment this because we might have a link frame
 * (if show links is on), so we have to extend our extended bounds
 * a bit.
 */
{
    NXRect linkBounds, *retval;
    float linkFrameThickness = NXLinkFrameThickness();

    linkBounds = bounds;
    linkBounds.origin.x -= linkFrameThickness;
    linkBounds.size.width += linkFrameThickness * 2.0;
    linkBounds.origin.y -= linkFrameThickness;
    linkBounds.size.height += linkFrameThickness;

    retval = [super getExtendedBounds:theRect];

    return NXUnionRect(&linkBounds, retval);
}

- (BOOL)constrainByDefault;
/*
 * Icons and link buttons look funny outside their natural
 * aspect ratio, so we constrain them (by default) to keep
 * their natural ratio.  You can still use the Alternate key
 * to NOT constrain these.
 */
{
    return (amLinkButton || amIcon);
}

/* Methods overridden from superclass */

- (BOOL)isValid
{
    return image ? YES : NO;
}

- (BOOL)isOpaque
{
    return [[image bestRepresentation] isOpaque];
}

- (float)naturalAspectRatio
{
    if (!originalSize.height) return 0.0;
    return originalSize.width / originalSize.height;
}

- draw
/*
 * If we are resizing, we just draw a gray box.
 * If not, then we simply see if our bounds have changed
 * and update the NXImage object if they have.  Then,
 * if we do not allow alpha (i.e. this is a TIFF image),
 * we paint a white background square (we don't allow
 * alpha in our TIFF images since it won't print and
 * Draw is WYSIWYG).  Finally, we SOVER the image.
 * If we are not keeping the cache around, we tell
 * NXImage to toss its cached version of the image
 * via the message recache.
 *
 * If we are linked to something and the user has chosen
 * "Show Links", then linkOutlinesAreVisible, so we must
 * draw a link border around ourself.
 */
{
    NXRect r;
    NXPoint p;
    NXSize currentSize;

    if (bounds.size.width < 1.0 || bounds.size.height < 1.0) return self;

    if (DrawStatus == Resizing) {
	PSsetgray(NX_DKGRAY);
	PSsetlinewidth(0.0);
	PSrectstroke(bounds.origin.x, bounds.origin.y, bounds.size.width, bounds.size.height);
    } else if (image) {
	p = bounds.origin;
	[image getSize:&currentSize];
	if (currentSize.width != bounds.size.width || currentSize.height != bounds.size.height) {
	    if ([image isScalable]) {
		[image setSize:&bounds.size];
	    } else {
		p.x = bounds.origin.x + floor((bounds.size.width - currentSize.width) / 2.0 + 0.5);
		p.y = bounds.origin.y + floor((bounds.size.height - currentSize.height) / 2.0 + 0.5);
	    }
	}
	if ([[image bestRepresentation] isOpaque]) {
	    PSsetgray(NX_WHITE);
	    NXRectFill(&bounds);
	}
	[image composite:NX_SOVER toPoint:&p];
	if (dontCache && NXDrawingStatus == NX_DRAWING) [image recache];
	if ((NXDrawingStatus == NX_DRAWING) && !amLinkButton && [[link manager] areLinkOutlinesVisible]) {
	    r.origin.x = floor(bounds.origin.x);
	    r.origin.y = floor(bounds.origin.y);
	    r.size.width = floor(bounds.origin.x + bounds.size.width + 0.99) - r.origin.x;
	    r.size.height = floor(bounds.origin.y + bounds.size.height + 0.99) - r.origin.y;
	    NXFrameLinkRect(&r, YES);	// YES means "is a destination link"
	}
    }

    return self;
}

/* Direct writing of EPS or TIFF. */

- (BOOL)canEmitEPS
/*
 * If we have a representation that can provide EPS directly, then,
 * if we are copying PostScript to the Pasteboard and this Image is the
 * only Graphic selected, then we might as well just have the EPS which
 * represents this Image go straight to the Pasteboard rather than
 * wrapping it up in the copyPSCodeInside: wrappers.  Of course, we
 * can only do that if we haven't been resized.
 *
 * See gvPasteboard.m's writePSToStream:.
 */
{
    List *reps = [image representationList];
    int i = [reps count];

    if (originalSize.width == bounds.size.width && originalSize.height == bounds.size.height) {
	while (i--) {
	    if ([[reps objectAt:i] respondsTo:@selector(getEPS:length:)]) {
		return YES;
	    }
	}
    }

    return NO;
}

- writeEPSToStream:(NXStream *)stream
/*
 * If canEmitEPS above returns YES, then we can write ourself out directly
 * as EPS.  This method does that.
 */
{
    List *reps = [image representationList];
    int i = [reps count];
    char *data;
    int length;

    while (i--) {
	if ([[reps objectAt:i] respondsTo:@selector(getEPS:length:)]) {
	    [[reps objectAt:i] getEPS:&data length:&length];
	    NXWrite(stream, data, length);
	    return self;			// should I free data before returning?
	}
    }

    return self;
}

- (BOOL)canEmitTIFF
/*
 * Similar to canEmitEPS, except its for TIFF.
 */
{
    return (originalSize.width == bounds.size.width && originalSize.height == bounds.size.height);
}

- writeTIFFToStream:(NXStream *)stream
/*
 * Ditto above.
 */
{
    [image writeTIFF:stream allRepresentations:YES];
    return self;
}

/* Caching. */

- setCacheable:(BOOL)flag
{
    dontCache = flag ? NO : YES;
    return self;
}

- (BOOL)isCacheable
{
    return !dontCache;
}

/* Archiving. */

- write:(NXTypedStream *)stream
/*
 * All that is needed to archive the NXImage.
 */
{
    [super write:stream];
    NXWriteType(stream, "c", &amLinkButton);
    NXWriteType(stream, "c", &amIcon);
    if (!amLinkButton) {
	NXWriteObject(stream, image);
	NXWriteSize(stream, &originalSize);
    }
    return self;
}

- read:(NXTypedStream *)stream
/*
 * This contains lots of compatibility code for
 * interim versions.  See if you can figure out the
 * various ways we approached archiving link info!
 */
{
    BOOL alphaOk;
    NXRect savedBounds;
    int version, linkNumber;

    [super read:stream];
    version = NXTypedStreamClassVersion(stream, "Image");
    if (version > 5) NXReadType(stream, "c", &amLinkButton);
    if (version > 6) NXReadType(stream, "c", &amIcon);
    if (amLinkButton) {
	savedBounds = bounds;
	[self doInitFromImage:[[NXImage findImageNamed:"NXLinkButton"] copy]];
	bounds = savedBounds;
    } else {
	image = NXReadObject(stream);
	NXReadSize(stream, &originalSize);
    }
    if (version <= 2) NXReadTypes(stream, "c", &alphaOk);
    if (version == 4) {
	NXReadObject(stream);	// used to be the NXDataLink
    } else if (version > 2 && version < 6) {
	NXReadTypes(stream, "i", &linkNumber);
    }

    return self;
}

@end
